﻿using DataService.Core.Collections;
using DataService.Core.Common;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace DataService.Core.Reflection
{
    /// <summary>程序集辅助类。使用Create创建，保证每个程序集只有一个辅助类</summary>
    public class AssemblyHelper
    {
        #region 属性
        /// <summary>程序集</summary>
        public Assembly Asm { get; }

        private String _Name;
        /// <summary>名称</summary>
        public String Name => _Name ??= "" + Asm.GetName().Name;

        private String _Version;
        /// <summary>程序集版本</summary>
        public String Version => _Version ??= "" + Asm.GetName().Version;

        private String _Title;
        /// <summary>程序集标题</summary>
        public String Title => _Title ??= "" + Asm.GetCustomAttributeValue<AssemblyTitleAttribute, String>();

        private String _FileVersion;
        /// <summary>文件版本</summary>
        public String FileVersion
        {
            get
            {
                if (_FileVersion == null)
                {
                    var ver = Asm.GetCustomAttributeValue<AssemblyInformationalVersionAttribute, String>();
                    if (!ver.IsNullOrEmpty())
                    {
                        var p = ver.IndexOf('+');
                        if (p > 0) ver = ver[..p];
                    }
                    _FileVersion = ver;
                }

                if (_FileVersion == null) _FileVersion = Asm.GetCustomAttributeValue<AssemblyFileVersionAttribute, String>();

                if (_FileVersion == null) _FileVersion = "";

                return _FileVersion;
            }
        }

        private DateTime? _Compile;
        /// <summary>编译时间</summary>
        public DateTime Compile => _Compile ??= GetCompileTime(Version);

        private String _Company;
        /// <summary>公司名称</summary>
        public String Company => _Company ??= "" + Asm.GetCustomAttributeValue<AssemblyCompanyAttribute, String>();

        private String _Description;
        /// <summary>说明</summary>
        public String Description => _Description ??= "" + Asm.GetCustomAttributeValue<AssemblyDescriptionAttribute, String>();

        /// <summary>获取包含清单的已加载文件的路径或 UNC 位置。</summary>
        public String Location
        {
            get
            {
                try
                {
                    return Asm == null || Asm.IsDynamic ? null : Asm.Location;
                }
                catch { return null; }
            }
        }
        #endregion

        #region 构造
        private AssemblyHelper(Assembly asm) => Asm = asm;

        private static readonly ConcurrentDictionary<Assembly, AssemblyHelper> cache = new ConcurrentDictionary<Assembly, AssemblyHelper>();
        /// <summary>创建程序集辅助对象</summary>
        /// <param name="asm"></param>
        /// <returns></returns>
        public static AssemblyHelper Create(Assembly asm)
        {
            if (asm == null) return null;

            return cache.GetOrAdd(asm, key => new AssemblyHelper(key));
        }

        static AssemblyHelper()
        {

            
        }



        #endregion

        #region 扩展属性
        //private IEnumerable<Type> _Types;
        /// <summary>类型集合，当前程序集的所有类型，包括私有和内嵌，非内嵌请直接调用Asm.GetTypes()</summary>
        public IEnumerable<Type> Types
        {
            get
            {
                Type[] ts;
                try
                {
                    ts = Asm.GetTypes();
                }
                catch (ReflectionTypeLoadException ex)
                {
                    ts = ex.Types;
                }
                if (ts == null || ts.Length <= 0) yield break;

                // 先遍历一次ts，避免取内嵌类型带来不必要的性能损耗
                foreach (var item in ts)
                {
                    if (item != null) yield return item;
                }

                var queue = new Queue<Type>(ts);
                while (queue.Count > 0)
                {
                    var item = queue.Dequeue();
                    if (item == null) continue;

                    var ts2 = item.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
                    if (ts2 != null && ts2.Length > 0)
                    {
                        // 从下一个元素开始插入，让内嵌类紧挨着主类
                        //Int32 k = i + 1;
                        foreach (var elm in ts2)
                        {
                            //if (!list.Contains(item)) list.Insert(k++, item);
                            // Insert将会导致大量的数组复制
                            queue.Enqueue(elm);

                            yield return elm;
                        }
                    }
                }
            }
        }

        /// <summary>是否系统程序集</summary>
        public Boolean IsSystemAssembly => CheckSystem(Asm);

        private static Boolean CheckSystem(Assembly asm)
        {
            if (asm == null) return false;

            var name = asm.FullName;
            if (name.EndsWith("PublicKeyToken=b77a5c561934e089")) return true;
            if (name.EndsWith("PublicKeyToken=b03f5f7f11d50a3a")) return true;
            if (name.EndsWith("PublicKeyToken=89845dcd8080cc91")) return true;
            if (name.EndsWith("PublicKeyToken=31bf3856ad364e35")) return true;

            return false;
        }
        #endregion

        #region 静态属性
        /// <summary>入口程序集</summary>
        public static AssemblyHelper Entry => Create(Assembly.GetEntryAssembly());
        /// <summary>
        /// 加载过滤器，如果返回 false 表示跳过加载。
        /// </summary>
        public static Func<String, Boolean> ResolveFilter { get; set; }
        #endregion

        #region 方法
        private readonly ConcurrentDictionary<String, Type> typeCache2 = new ConcurrentDictionary<String, Type>();
        /// <summary>从程序集中查找指定名称的类型</summary>
        /// <param name="typeName"></param>
        /// <returns></returns>
        public Type GetType(String typeName)
        {
            if (String.IsNullOrEmpty(typeName)) throw new ArgumentNullException(nameof(typeName));

            return typeCache2.GetOrAdd(typeName, GetTypeInternal);
        }

        /// <summary>在程序集中查找类型</summary>
        /// <param name="typeName"></param>
        /// <returns></returns>
        private Type GetTypeInternal(String typeName)
        {
            var type = Asm.GetType(typeName);
            if (type != null) return type;

            // 如果没有包含圆点，说明其不是FullName
            if (!typeName.Contains('.'))
            {
                // 遍历所有类型，包括内嵌类型
                foreach (var item in Types)
                {
                    if (item.Name == typeName) return item;
                }
            }

            return null;
        }
        #endregion

        #region 插件


        /// <summary>根据名称获取类型</summary>
        /// <param name="typeName">类型名</param>
        /// <param name="isLoadAssembly">是否从未加载程序集中获取类型。使用仅反射的方法检查目标类型，如果存在，则进行常规加载</param>
        /// <returns></returns>
        public static Type GetType(String typeName, Boolean isLoadAssembly)
        {
            var type = Type.GetType(typeName);
            if (type != null) return type;

            // 数组
            if (typeName.EndsWith("[]"))
            {
                var elemType = GetType(typeName[0..^2], isLoadAssembly);
                if (elemType == null) return null;

                return elemType.MakeArrayType();
            }

            // 加速基础类型识别，忽略大小写
            if (!typeName.Contains('.'))
            {
                foreach (var item in Enum.GetNames(typeof(TypeCode)))
                {
                    if (typeName.EqualIgnoreCase(item))
                    {
                        type = Type.GetType("System." + item);
                        if (type != null) return type;
                    }
                }
            }

            // 尝试本程序集
            var asms = new[] {
                Create(Assembly.GetExecutingAssembly()),
                Create(Assembly.GetCallingAssembly()),
                Create(Assembly.GetEntryAssembly()) };
            var loads = new List<AssemblyHelper>();

            foreach (var asm in asms)
            {
                if (asm == null || loads.Contains(asm)) continue;
                loads.Add(asm);

                type = asm.GetType(typeName);
                if (type != null) return type;
            }

            // 尝试所有程序集
            foreach (var asm in GetAssemblies(null))
            {
                if (loads.Contains(asm)) continue;
                loads.Add(asm);

                type = asm.GetType(typeName);
                if (type != null) return type;
            }

            // 尝试加载只读程序集
            if (!isLoadAssembly) return null;
            //若又必要则从未加载的程序集中加载dll
            //foreach (var asm in ReflectionOnlyGetAssemblies())
            //{
            //    type = asm.GetType(typeName);
            //    if (type != null)
            //    {
            //        // 真实加载
            //        var file = asm.Asm.Location;
            //        try
            //        {
            //            type = null;
            //            var asm2 = Assembly.LoadFile(file);
            //            var type2 = Create(asm2).GetType(typeName);
            //            if (type2 == null) continue;
            //            type = type2;

            //        }
            //        catch (Exception ex)
            //        {
            //            TraceLog.WriteLine(ex.ToString());
            //        }

            //        return type;
            //    }
            //}

            return null;
        }
        #endregion

        #region 静态加载
        /// <summary>获取指定程序域所有程序集</summary>
        /// <param name="domain"></param>
        /// <returns></returns>
        public static IEnumerable<AssemblyHelper> GetAssemblies(AppDomain domain)
        {
            if (domain == null) domain = AppDomain.CurrentDomain;

            var asms = domain.GetAssemblies();
            if (asms == null || asms.Length <= 0) return Enumerable.Empty<AssemblyHelper>();

            return from e in asms select Create(e);

        }

        private static ICollection<String> _AssemblyPaths;


        private static readonly ConcurrentHashSet<String> _BakImages = new ConcurrentHashSet<String>();
        /// <summary>只反射加载指定路径的所有程序集</summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static IEnumerable<AssemblyHelper> ReflectionOnlyLoad(String path)
        {
            if (!Directory.Exists(path)) yield break;

            // 先返回已加载的只加载程序集
            var loadeds2 = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().Select(e => Create(e)).ToList();

            // 再去遍历目录
            var ss = Directory.GetFiles(path, "*.*", SearchOption.TopDirectoryOnly);
            if (ss == null || ss.Length <= 0) yield break;

            var loadeds = GetAssemblies(null).ToList();

            var ver = new Version(Assembly.GetExecutingAssembly().ImageRuntimeVersion.TrimStart('v'));

            foreach (var item in ss)
            {
                // 仅尝试加载dll
                if (!item.EndsWithIgnoreCase(".dll")) continue;
                if (_BakImages.Contains(item)) continue;

                if (loadeds.Any(e => e.Location.EqualIgnoreCase(item)) ||
                    loadeds2.Any(e => e.Location.EqualIgnoreCase(item))) continue;

                var asm = ReflectionOnlyLoadFrom(item, ver);
                if (asm == null) continue;

                // 不搜索系统程序集，优化性能
                if (CheckSystem(asm)) continue;

                // 尽管目录不一样，但这两个可能是相同的程序集
                // 这里导致加载了不同目录的同一个程序集，然后导致对象容器频繁报错
                //if (loadeds.Any(e => e.Asm.FullName.EqualIgnoreCase(asm.FullName)) ||
                //    loadeds2.Any(e => e.Asm.FullName.EqualIgnoreCase(asm.FullName))) continue;
                // 相同程序集不同版本，全名不想等
                if (loadeds.Any(e => e.Asm.GetName().Name.EqualIgnoreCase(asm.GetName().Name)) ||
                    loadeds2.Any(e => e.Asm.GetName().Name.EqualIgnoreCase(asm.GetName().Name))) continue;

                var asmx = Create(asm);
                if (asmx != null) yield return asmx;
            }
        }

        /// <summary>只反射加载指定路径的所有程序集</summary>
        /// <param name="file"></param>
        /// <param name="ver"></param>
        /// <returns></returns>
        public static Assembly ReflectionOnlyLoadFrom(String file, Version ver)
        {
            try
            {
                return Assembly.LoadFrom(file);
            }
            catch
            {
                _BakImages.TryAdd(file);
                return null;
            }
        }

        /// <summary>获取当前应用程序的所有程序集，不包括系统程序集，仅限本目录</summary>
        /// <returns></returns>
        public static List<AssemblyHelper> GetMyAssemblies()
        {
            var list = new List<AssemblyHelper>();
            var hs = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
            var cur = AppDomain.CurrentDomain.BaseDirectory;
            foreach (var asmx in GetAssemblies(null))
            {
                // 加载程序集列表很容易抛出异常，全部屏蔽
                try
                {
                    if (asmx.FileVersion.IsNullOrEmpty()) continue;

                    var file = "";

                    if (file.IsNullOrEmpty()) file = asmx.Asm.Location;
                    if (file.IsNullOrEmpty()) continue;

                    if (file.StartsWith("file:///"))
                    {
                        file = file.TrimStart("file:///");
                        if (Path.DirectorySeparatorChar == '\\')
                            file = file.Replace('/', '\\');
                        else
                            file = file.Replace('\\', '/').EnsureStart("/");
                    }
                    if (!file.StartsWithIgnoreCase(cur)) continue;

                    if (!hs.Contains(file))
                    {
                        hs.Add(file);
                        list.Add(asmx);
                    }
                }
                catch { }
            }
            return list;
        }


        #endregion



        #region 辅助
        /// <summary>根据版本号计算得到编译时间</summary>
        /// <param name="version"></param>
        /// <returns></returns>
        public static DateTime GetCompileTime(String version)
        {
            var ss = version?.Split(new Char[] { '.' });
            if (ss == null || ss.Length < 4) return DateTime.MinValue;

            var d = ss[2].ToInt();
            var s = ss[3].ToInt();
            var y = DateTime.Today.Year;

            // 指定年月日的版本格式 1.0.yyyy.mmdd-betaHHMM
            if (d <= y && d >= y - 10)
            {
                var dt = new DateTime(y, 1, 1);
                if (s > 0)
                {
                    if (s >= 200) dt = dt.AddMonths(s / 100 - 1);
                    s %= 100;
                    if (s > 1) dt = dt.AddDays(s - 1);
                }
                else
                {
                    var str = ss[3];
                    var p = str.IndexOf('-');
                    if (p > 0)
                    {
                        s = str[..p].ToInt();
                        if (s > 0)
                        {
                            if (s >= 200) dt = dt.AddMonths(s / 100 - 1);
                            s %= 100;
                            if (s > 1) dt = dt.AddDays(s - 1);
                        }

                        if (str.Length >= 4 + 1 + 4)
                        {
                            s = str[^4].ToInt();
                            if (s > 0) dt = dt.AddHours(s / 100).AddMinutes(s % 100);
                        }
                    }
                }

                return dt;
            }
            else
            {
                var dt = new DateTime(2000, 1, 1);
                dt = dt.AddDays(d).AddSeconds(s * 2);

                return dt;
            }
        }
        #endregion
    }
}
